using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;

using HtmlAgilityPack;
using System.Web.UI.WebControls;

namespace ASPNETLibrary.SharePoint.WebParts.GeneratorEngine
{
    /// <summary>
    /// This class will implement generation of classes that inherit from
    /// BaseSkinnedWebPart based on skins (ASCX controls) for the web parts.
    /// </summary>
    public class SkinnedWebPartsGenerator
    {
        /// <summary>
        /// The index of ':' character in ASP tags
        /// </summary>
        private const int AspColonCharactersIndex = 3;
        private TextWriter _output;
        private string _dummyFullyQualifiedName;
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="errorOutput">TextWriter where this class will write the generation information (status, etc)</param>
        public SkinnedWebPartsGenerator(TextWriter output)
        {
            this._output = output;

            //Instantiate a TextBox control to read the full qualified name of
            //hosting assembly
            TextBox dummyTextBox = new TextBox();
            Type dummyType = dummyTextBox.GetType();
            this._dummyFullyQualifiedName = dummyType.AssemblyQualifiedName;
        }

        #region [GenerateSkinnedWebPart]
        /// <summary>
        /// This method will generate a skinned web part class based on its
        /// skin (<paramref name="skinFilenamePath"/>). It will save the generated
        /// class into a new text file on location specified with <paramref name="outputFilenamePath"/>
        /// </summary>
        /// <param name="skinFilenamePath">The full path to skin (ASCX) file</param>
        /// <param name="outputFilenamePath">The full path to output file (file that will be generated)</param>
        /// <param name="overwriteExistingFile">Specifies if output file should be overwriten
        /// if it already exists</param>
        /// <returns>Returns true if generation succeeded and false otherwise</returns>
        public bool GenerateSkinnedWebPart(string skinFilenamePath,
            string outputFilenamePath, string webPartsNamespace, bool overwriteExistingFile)
        {
            bool retVal = true;

            this.ShowInfo("Checking files paths...");
            if (String.IsNullOrEmpty(skinFilenamePath))
            {
                this.ShowError("The path to skin file is not set!");
                retVal = false;
            }

            if (String.IsNullOrEmpty(outputFilenamePath))
            {
                this.ShowError("The path to output file is not set!");
                retVal = false;
            }

            if (retVal)
            {
                string outputFilenameGeneratedPath = this.FormatToGeneratedFilename(outputFilenamePath);

                FileInfo skinFile = new FileInfo(skinFilenamePath);
                FileInfo outputFile = new FileInfo(outputFilenamePath);
                FileInfo outputFileGenerated = new FileInfo(outputFilenameGeneratedPath);
                if (!skinFile.Exists)
                {
                    this.ShowError(String.Format("Skin file '{0}' doesn't exist!", skinFilenamePath));
                    retVal = false;
                }
                if (retVal)
                {
                    //If overwriteExistingFile is set to false then we need
                    //to check if file already exists
                    bool generatePrimarySourceFile = true;
                    if (outputFile.Exists && !overwriteExistingFile)
                    {
                        generatePrimarySourceFile = false;
                    }
                    retVal = this.GenerateSkinnedWebPart(skinFile, outputFile, outputFileGenerated, webPartsNamespace, generatePrimarySourceFile);
                }
            }

            return retVal;
        }

        /// <summary>
        /// This method will insert "generated" into the filename before the extension
        /// </summary>
        /// <param name="originalFilename">Original filename</param>
        /// <returns>Returns a filename with inserted "generated" string</returns>
        private string FormatToGeneratedFilename(string originalFilename)
        {
            string retVal = String.Empty;

            int lastDotIndex = originalFilename.LastIndexOf('.');
            retVal = originalFilename.Insert(lastDotIndex + 1, "generated.");

            return retVal;
        }
        #endregion [GenerateSkinnedWebPart]

        #region [Private methods]

        #region [GenerateSkinnedWebPart]
        private bool GenerateSkinnedWebPart(FileInfo skinFile, FileInfo outputFile,
            FileInfo outputFileGenerated, string webPartsNamespace, bool generatePrimarySourceFile)
        {
            bool retVal = false;

            this.ShowInfo("Starting with generation...");
            //Load all server controls from the skin

            List<Control> serverControls = this.LoadAllServerControls(skinFile);
            if (serverControls != null)
            {
                string webPartsContent = String.Empty;
                string webPartsGeneratedContent = String.Empty;

                string webPartsGuid = Guid.NewGuid().ToString();
                string webPartsName = this.GetFilename(outputFile);
                string memberVariablesSection = String.Empty;// this.GetMemberVariablesSection(serverControls);
                string constantsSection = String.Empty;// this.GetConstantsSection(serverControls);
                string attachControlsSection = String.Empty;// this.GetAttachControlsSection(serverControls);
                string requiredControlsSection = String.Empty;// this.GetRequiredControlsSection(serverControls, webPartsName);

                string tabCharacter = Convert.ToChar(9).ToString();
                foreach (Control control in serverControls)
                {
                    string id = control.ID;
                    string idCamelCase = this.GetIDInCamelCase(id);
                    string controlsType = this.GetControlsType(control);

                    memberVariablesSection += String.Format(TemplatesResources.MemberVariablesSectionTemplate, controlsType, idCamelCase);
                    constantsSection += String.Format(TemplatesResources.ConstantsSectionTemplate, id);
                    attachControlsSection += String.Format(TemplatesResources.AttachControlsSectionTemplate, idCamelCase, controlsType, webPartsName, id);
                    requiredControlsSection += String.Format(TemplatesResources.RequiredControlsSectionTemplate, idCamelCase, webPartsName, id);

                    memberVariablesSection += Environment.NewLine;
                    constantsSection += Environment.NewLine;
                    attachControlsSection += Environment.NewLine;
                    requiredControlsSection += Environment.NewLine;
                }

                webPartsContent = String.Format(TemplatesResources.SkinnedWebPartTemplate,
                    webPartsNamespace, webPartsGuid, webPartsName);

                webPartsGeneratedContent = String.Format(TemplatesResources.SkinnedWebPartGeneratedTemplate,
                    webPartsNamespace, webPartsName, memberVariablesSection,
                    constantsSection, attachControlsSection, requiredControlsSection);

                this.ShowInfo("Writing to first file...");
                if (generatePrimarySourceFile)
                {
                    this.WriteOutputFile(outputFile, webPartsContent);
                }
                else
                {
                    this.ShowInfo("Skipping first file generation.");
                }

                this.ShowInfo("Writing to second file...");
                this.WriteOutputFile(outputFileGenerated, webPartsGeneratedContent);

                retVal = true;
            }
            else
            {
                this.ShowError("There are no server controls! Generation will not be performed.");
            }

            this.ShowInfo("Finished with generation...");

            return retVal;
        }

        private string GetFilename(FileInfo outputFile)
        {
            int dotsIndex = outputFile.Name.IndexOf('.');
            return outputFile.Name.Substring(0, dotsIndex);
        }

        #region [WriteOutputFile]
        private void WriteOutputFile(FileInfo outputFile, string webPartsContent)
        {
            using (StreamWriter sw = new StreamWriter(outputFile.FullName, false))
            {
                sw.Write(webPartsContent);

                sw.Close();
            }
        }
        #endregion [WriteOutputFile]

        private string GetIDInCamelCase(string id)
        {
            return id.Substring(0, 1).ToLower() + id.Substring(1);
        }

        private string GetControlsType(Control control)
        { 
            //Return only the name of the control without the namespace
            string fullTypesName = control.GetType().Name;
            int namesIndex = fullTypesName.LastIndexOf('.') + 1;
            return fullTypesName.Substring(namesIndex);
        }
        #endregion [GenerateSkinnedWebPart]

        #region [LoadAllServerControls]
        private List<Control> LoadAllServerControls(FileInfo skinFile)
        {
            //First load the skin content into HtmlDocument (DOM representation of the skin file)
            HtmlDocument skin = new HtmlDocument();
            string controlContent = null;
            using (StreamReader sr = new StreamReader(skinFile.FullName))
            {
                controlContent = sr.ReadToEnd();
                sr.Close();
            }
            skin.LoadHtml(controlContent);

            //Iterate through all ASP.NET controls (tags that start with 'asp:')
            //add populate a list of Controls based on parsed values
            List<Control> serverControls = null;
            HtmlNodeCollection nodes = skin.DocumentNode.SelectNodes("//*[substring(name(),1,3) = \"asp\" and @runat = \"server\"]");
            if (nodes != null && nodes.Count > 0)
            {
                serverControls = new List<Control>(nodes.Count);
                foreach (HtmlNode serverControlNode in nodes)
                {
                    this.ParseServerControl(serverControlNode, serverControls);
                }
            }
            return serverControls;
        }
        #endregion [LoadAllServerControls]

        #region [ParseServerControl]
        private void ParseServerControl(HtmlNode serverControlNode, List<Control> serverControls)
        {
            string rawControlsType = serverControlNode.NameNoLowerCase;
            string controlsID = serverControlNode.Id;

            //If third character is not ':' then we will just ignore the node
            int colonIndex = rawControlsType.IndexOf(':', 0);
            if (colonIndex == SkinnedWebPartsGenerator.AspColonCharactersIndex)
            {
                string controlsType = rawControlsType.Substring(SkinnedWebPartsGenerator.AspColonCharactersIndex + 1);
                string fullControlsType = this.GetFullControlsType(controlsType);
                    
                Control control = (Control)Activator.CreateInstance(System.Type.GetType(fullControlsType));
                control.ID = controlsID;

                serverControls.Add(control);
            }
        }
        #endregion [ParseServerControl]

        #region [GetFullControlsType]
        private string GetFullControlsType(string controlsType)
        {
            return this._dummyFullyQualifiedName.Replace("TextBox", controlsType);
        }
        #endregion [GetFullControlsType]

        #region [Show Messages]
        private void ShowError(string message)
        {
            this._output.WriteLine("Error: " + message);
        }
        private void ShowInfo(string message)
        {
            this._output.WriteLine("Info: " + message);
        }
        #endregion [Show Messages]

        #endregion [Private methods]
    }
}
